package com.xqboss.apps.service.synthesis;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Pair;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xqboss.apps.constant.LockKey;
import com.xqboss.apps.domain.box.Box;
import com.xqboss.apps.domain.box.BoxNft;
import com.xqboss.apps.domain.member.SysUserNft;
import com.xqboss.apps.domain.nft.Nft;
import com.xqboss.apps.domain.nft.NftNumber;
import com.xqboss.apps.domain.synthesis.*;
import com.xqboss.apps.domain.sys.SysAnnouncementMsg;
import com.xqboss.apps.domain.user.SysUserWallet;
import com.xqboss.apps.domain.user.UserBox;
import com.xqboss.apps.domain.user.UserIdentityRank;
import com.xqboss.apps.dto.synthesis.*;
import com.xqboss.apps.dto.user.OpenBoxDto;
import com.xqboss.apps.dto.user.UserNftListDto;
import com.xqboss.apps.enums.box.BoxSequenceEnum;
import com.xqboss.apps.enums.nft.CommonBooleanEnum;
import com.xqboss.apps.enums.nft.NftDispatchSourceEnum;
import com.xqboss.apps.enums.nft.NftTypeEnum;
import com.xqboss.apps.enums.nft.UserNftStatusEnum;
import com.xqboss.apps.enums.synthesis.SynthesisActivityTypeEnum;
import com.xqboss.apps.enums.synthesis.SynthesisStatusEnum;
import com.xqboss.apps.enums.synthesis.SynthesisTypeEnum;
import com.xqboss.apps.enums.synthesis.SynthesisWhiteTypeEnum;
import com.xqboss.apps.enums.user.*;
import com.xqboss.apps.mapper.member.SysUserNftMapper;
import com.xqboss.apps.mapper.synthesis.*;
import com.xqboss.apps.service.box.BoxService;
import com.xqboss.apps.service.nft.NftNumberService;
import com.xqboss.apps.service.nft.NftService;
import com.xqboss.apps.service.sys.SysAnnouncementMsgService;
import com.xqboss.apps.service.user.*;
import com.xqboss.apps.util.mh.BeanCopierUtil;
import com.xqboss.apps.vo.equipment.EquipmentInfoVo;
import com.xqboss.apps.vo.synthesis.*;
import com.xqboss.apps.vo.sys.SysAnnouncementMsgVo;
import com.xqboss.apps.vo.user.UserNFTItemVo;
import com.xqboss.common.core.redis.lock.LockValue;
import com.xqboss.common.core.redis.lock.RedisLock;
import com.xqboss.common.core.redis.lock.RedisLockUtils;
import com.xqboss.common.core.text.StrFormatter;
import com.xqboss.common.exception.ServiceException;
import com.xqboss.common.utils.LocalDateUtils;
import com.xqboss.common.utils.SecurityUtils;
import com.xqboss.common.utils.TransactionalManage;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.BooleanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;

/**
 * <p>
 * 合成活动 服务实现类
 * </p>
 *
 * @author 木鱼
 * @since 2023-11-17
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class SynthesisService extends ServiceImpl<SynthesisMapper, Synthesis> {
    private final SynthesisPlanResultMapper synthesisPlanResultMapper;
    private final TransactionalManage transactionalManage;
    private final SynthesisStockMapper synthesisStockMapper;
    private final SynthesisPlanMapper synthesisPlanMapper;
    private final SynthesisPlanStatusService synthesisPlanStatusService;
    private final SynthesisPlanConsumeMapper synthesisPlanConsumeMapper;
    private final SysUserNftService sysUserNftService;
    private final SysUserNftMapper sysUserNftMapper;
    private final UserBoxService userBoxService;
    private final SysUserWalletService sysUserWalletService;
    private final SynthesisHoldService synthesisHoldService;
    private final SynthesisHoldConditionService synthesisHoldConditionService;
    private final SynthesisWhiteService synthesisWhiteService;
    private final RedisLockUtils redisLockUtils;
    private final NftService nftService;
    private final NftNumberService nftNumberService;
    private final BoxService boxService;
    private final SynthesisPlanService synthesisPlanService;
    private final SynthesisUserRecordService synthesisUserRecordService;
    private final SynthesisRecordConsumeResultService synthesisRecordConsumeResultService;
    private final SysAnnouncementMsgService sysAnnouncementMsgService;
    private final SynthesisSimulatedRecordService synthesisSimulatedRecordService;
    private final UserIdentityRankService userIdentityRankService;
    private final UserAssetWrenchRecordService userAssetWrenchRecordService;
    private final SynthesisBoxRuleService synthesisPlanResultBoxRuleService;
    private final UserAssetLidouService userAssetLidouService;

    public List<SynthesisPageVo> selectWebPage(SynthesisPageDto request) {
        request.startPage();
        return baseMapper.selectWebPage(request);
    }

    public List<SynthesisPageVo> selectAppPage(SynthesisPageDto request) {
        request.startPage();
        return baseMapper.selectAppPage(request);
    }

    public WebSynthesisDetailVo selectBasic(Long id) {
        WebSynthesisDetailVo vo = baseMapper.selectBasic(id);
        if (Objects.isNull(vo)) {
            throw new ServiceException("合成活动不存在");
        }
        //查询方案详情
        SynthesisPlanDetailDto planDetail = synthesisPlanService.selectByIdBasic(vo.getPlanId());
        vo.setPlanDetail(planDetail);
        List<SynthesisPlanStatus> statusList = synthesisPlanStatusService.list(new LambdaQueryWrapper<SynthesisPlanStatus>()
                .eq(SynthesisPlanStatus::getSynthesisId, vo.getId()));
        //为了前端好返显身份等级
        if (vo.getIdentityRankId() != null) {
            UserIdentityRank rank = userIdentityRankService.getById(vo.getIdentityRankId());
            if ("竞技者".equals(rank.getIdentityName())) {
                vo.setParentIdentityRankId(rank.getIdentityId());
            }
        }
        vo.setStatus(statusList);
        return vo;
    }

    @Transactional(rollbackFor = Exception.class)
    public boolean saveBasic(SynthesisMainSaveUpdateDto record) {
        if (Objects.isNull(record) || record.getId() != null) {
            throw new ServiceException("参数错误");
        }
        //若持图开启，校验持图必填项
        if (record.getHold() == 1 && CollUtil.isEmpty(record.getHoldCondition())) {
            throw new ServiceException("持图开启，材料不能为空");
        }
        //若白名单开启，校验白名单合成时间和导入名单是否为空
        if (record.getWhite() == 1 && (CollUtil.isEmpty(record.getWhiteList()) || record.getWhiteTime() == null)) {
            throw new ServiceException("白名单开启，名单/合成时间不能为空");
        }

        //添加活动
        Synthesis synthesis = BeanUtil.copyProperties(record, Synthesis.class);
        int i = baseMapper.insert(synthesis);
        if (i != 1) {
            throw new ServiceException("添加活动失败");
        }
        //添加活动库存
        SynthesisStock stock = new SynthesisStock();
        stock.setId(synthesis.getId());
        stock.setNum(record.getNum() - record.getSimulatedSales());//当前库存 = 总库存 - 虚拟销量
        stock.setWhiteNum(record.getWhiteNum() - record.getWhiteSimulatedSales());
        stock.setSimulatedSales(record.getSimulatedSales());
        stock.setWhiteSimulatedSales(record.getWhiteSimulatedSales());
        stock.setTotalNum(record.getNum() + record.getWhiteNum());
        // 获取方案输出，锁定库存
        int totalStock = stock.getNum() + stock.getWhiteNum();
        List<SynthesisPlanResultDto> planResult = synthesisPlanService.getPlanResult(record.getPlanId());
        for (SynthesisPlanResultDto result : planResult) {
            switch (result.getType()) {
                case BOX:
                    boxService.lockStock(result.getThirdId(), totalStock * result.getProductNum().intValue());
                    break;
                case EQUIPMENT:
                case NORMAL:
                    nftService.lockStock(result.getThirdId(), totalStock * result.getProductNum().intValue());
                    break;
            }
        }
        synthesisStockMapper.insert(stock);

        //添加持图配置
        if (record.getHold() == 1) {
            SynthesisHold hold = new SynthesisHold();
            hold.setId(synthesis.getId());
            hold.setHoldTime(record.getHoldTime());
            hold.setType(record.getHoldType());
            synthesisHoldService.save(hold);
            //添加持图配置-材料
            if (CollUtil.isNotEmpty(record.getHoldCondition())) {
                record.getHoldCondition().forEach(condition -> {
                    condition.setSynthesisId(synthesis.getId());
                });
                synthesisHoldConditionService.saveBatch(record.getHoldCondition());
            }
        }

        //添加白名单合成
        if (record.getWhite() == 1) {
            if (CollUtil.isNotEmpty(record.getWhiteList())) {
                record.getWhiteList().forEach(white -> {
                    white.setSynthesisId(synthesis.getId());
                });
                synthesisWhiteService.saveBatch(record.getWhiteList());
            }
        }

        //添加规则方案分组方案状态
//        if (CollUtil.isNotEmpty(record.getStatus())) {
//            record.getStatus().forEach(s -> {s.setSynthesisId(synthesis.getId());});
//            synthesisPlanStatusService.saveBatch(record.getStatus());
//        }
        return true;
    }

    @Transactional(rollbackFor = Exception.class)
    public boolean updateBasic(SynthesisMainSaveUpdateDto record) {
        if (Objects.isNull(record) || record.getId() == null || record.getId() <= 0) {
            throw new ServiceException("参数错误");
        }
        if (record.getHold() == 1 && CollUtil.isEmpty(record.getHoldCondition())) {
            throw new ServiceException("请选择持图合成材料");
        }
        if (record.getWhite() == 1 && CollUtil.isEmpty(record.getWhiteList())) {
            throw new ServiceException("白名单列表不能为空");
        }
        Synthesis sys = getById(record.getId());
        if (Objects.isNull(sys)) {
            throw new ServiceException("活动不存在");
        }
        Synthesis synthesis = BeanUtil.copyProperties(record, Synthesis.class);
        Integer syn = baseMapper.updateInfoById(synthesis);
        if (syn != 1) {
            throw new ServiceException("更新失败");
        }

        SynthesisHold hold = new SynthesisHold();
        hold.setId(synthesis.getId());
        hold.setHoldTime(record.getHoldTime());
        hold.setType(record.getHoldType());

        //编辑持图配置
        if (record.getHold() == 0) {
            //关闭持图 删除材料
            synthesisHoldService.updateById(hold);
            synthesisHoldConditionService.remove(new LambdaQueryWrapper<SynthesisHoldCondition>()
                    .eq(SynthesisHoldCondition::getSynthesisId, record.getId()));
        } else {
            boolean update = synthesisHoldService.saveOrUpdate(hold);
            if (!update) {
                throw new ServiceException("编辑持图配置失败");
            }
            //编辑持图配置-材料
            if (CollUtil.isNotEmpty(record.getHoldCondition())) {
                record.getHoldCondition().forEach(t -> t.setSynthesisId(record.getId()));
                boolean b = synthesisHoldConditionService.saveOrUpdateBatch(record.getHoldCondition());
                if (!b) {
                    throw new ServiceException("编辑持图配置-材料失败");
                }
            }
        }

        if (record.getWhite() == 0) {
            //关闭白名单，删除白名单
            synthesisWhiteService.remove(new LambdaQueryWrapper<SynthesisWhite>()
                    .eq(SynthesisWhite::getSynthesisId, record.getId())
                    .eq(SynthesisWhite::getType, SynthesisWhiteTypeEnum.WHITE_LIMIT));
        } else {
            //编辑白名单合成
            if (CollUtil.isNotEmpty(record.getWhiteList())) {
                record.getWhiteList().forEach(w -> w.setSynthesisId(record.getId()));
                boolean b = synthesisWhiteService.saveOrUpdateBatch(record.getWhiteList());
                if (!b) {
                    throw new ServiceException("编辑白名单合成失败");
                }
            }
        }

        return true;
    }

    /**
     * 编辑配置
     *
     * @param params
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public boolean updateConfig(WebSynthesisEditDto params) throws Exception {
        Synthesis synthesis = getById(params.getId());
        if (Objects.isNull(synthesis)) {
            throw new ServiceException("合成活动不存在");
        }
        switch (params.getEditType()) {
            case PLAN_STATUS: {
                boolean b = synthesisPlanStatusService.update(new LambdaUpdateWrapper<SynthesisPlanStatus>()
                        .set(SynthesisPlanStatus::getStatus, params.getStatus())
                        .eq(SynthesisPlanStatus::getSynthesisId, synthesis.getId())
                        .eq(SynthesisPlanStatus::getPlanId, synthesis.getPlanId()));
                if (!b) {
                    throw new ServiceException("修改合成活动状态失败");
                }
            }
            break;
            case SYNTHETIC_PORTION: {
                transactionalManage.executeWithException(() -> {
                    switch (params.getDirection()) {
                        case IN:
                            // 增加库存
                            List<SynthesisPlanResultDto> planResult = synthesisPlanService.getPlanResult(synthesis.getPlanId());
                            for (SynthesisPlanResultDto result : planResult) {
                                switch (result.getType()) {
                                    case BOX:
                                        boxService.lockStock(result.getThirdId(), params.getNum() * result.getProductNum().intValue());
                                        break;
                                    case EQUIPMENT:
                                    case NORMAL:
                                        nftService.lockStock(result.getThirdId(), params.getNum() * result.getProductNum().intValue());
                                        break;
                                }
                            }
                            boolean b = synthesisStockMapper.addTotalStock(synthesis.getId(), params.getNum());
                            if (!b) {
                                throw new ServiceException("添加合成库存失败！");
                            }
                            break;
                        case OUT:
                            b = synthesisStockMapper.rollbackTotalStock(synthesis.getId(), params.getNum());
                            if (!b) {
                                throw new ServiceException("库存不足！");
                            }
                            planResult = synthesisPlanService.getPlanResult(synthesis.getPlanId());
                            for (SynthesisPlanResultDto result : planResult) {
                                switch (result.getType()) {
                                    case BOX:
                                        boxService.unlockStock(result.getThirdId(), params.getNum() * result.getProductNum().intValue());
                                        break;
                                    case EQUIPMENT:
                                    case NORMAL:
                                        nftService.unlockStock(result.getThirdId(), params.getNum() * result.getProductNum().intValue());
                                        break;
                                }
                            }
                            break;
                    }
                    return true;
                });
            }
            break;
            case SYNTHETIC_NUMBER: {
                boolean b = update(Wrappers.<Synthesis>lambdaUpdate()
                        .set(Synthesis::getUserMaxNum, params.getNum())
                        .eq(Synthesis::getId, params.getId()));
                if (!b) {
                    throw new ServiceException("修改总合成次数失败");
                }
            }
            break;
            case SYNTHETIC_SINGLE_NUMBER: {
                boolean b = update(Wrappers.<Synthesis>lambdaUpdate()
                        .set(Synthesis::getUserSingleMaxNum, params.getNum())
                        .eq(Synthesis::getId, params.getId()));
                if (!b) {
                    throw new ServiceException("修改用户单次合成次数失败");
                }
            }
            break;
            case SIMULATED_SALES: {
                transactionalManage.executeWithException(() -> {
                    boolean b = synthesisStockMapper.addSimulatedSales(synthesis.getId(), params.getNum());
                    if(!b){
                        throw new ServiceException("添加虚拟库存失败：库存不足！");
                    }
                    List<SynthesisPlanResultDto> planResult = synthesisPlanService.getPlanResult(synthesis.getPlanId());
                    for (SynthesisPlanResultDto result : planResult) {
                        switch (result.getType()) {
                            case BOX:
                                boxService.unlockStock(result.getThirdId(), params.getNum() * result.getProductNum().intValue());
                                break;
                            case EQUIPMENT:
                            case NORMAL:
                                nftService.unlockStock(result.getThirdId(), params.getNum() * result.getProductNum().intValue());
                                break;
                        }
                    }
                    //添加虚拟销量记录
                    SynthesisSimulatedRecord simulatedRecord = new SynthesisSimulatedRecord();
                    simulatedRecord.setAmount(params.getNum());
                    simulatedRecord.setSynthesisId(params.getId());
                    simulatedRecord.setUserId(SecurityUtils.getUserId());
                    simulatedRecord.setUserName(SecurityUtils.getUsername());
                    simulatedRecord.setType(1);
                    synthesisSimulatedRecordService.save(simulatedRecord);
                    return true;
                });
            }
            break;
            case WHITE_SIMULATED_SALES: {
                transactionalManage.executeWithException(() -> {
                    boolean b = synthesisStockMapper.addWhiteSimulatedSales(synthesis.getId(), params.getNum());
                    if(!b){
                        throw new ServiceException("添加白名单虚拟库存失败：库存不足！");
                    }
                    List<SynthesisPlanResultDto> planResult = synthesisPlanService.getPlanResult(synthesis.getPlanId());
                    for (SynthesisPlanResultDto result : planResult) {
                        switch (result.getType()) {
                            case BOX:
                                boxService.unlockStock(result.getThirdId(), params.getNum() * result.getProductNum().intValue());
                                break;
                            case EQUIPMENT:
                            case NORMAL:
                                nftService.unlockStock(result.getThirdId(), params.getNum() * result.getProductNum().intValue());
                                break;
                        }
                    }
                    //添加虚拟销量记录
                    SynthesisSimulatedRecord simulatedRecord = new SynthesisSimulatedRecord();
                    simulatedRecord.setAmount(params.getNum());
                    simulatedRecord.setSynthesisId(params.getId());
                    simulatedRecord.setUserId(SecurityUtils.getUserId());
                    simulatedRecord.setUserName(SecurityUtils.getUsername());
                    simulatedRecord.setType(2);
                    synthesisSimulatedRecordService.save(simulatedRecord);
                    return true;
                });
            }
            break;
            case IS_WHITE: {
                transactionalManage.executeWithException(() -> {
                    switch (params.getDirection()) {
                        case IN:
                            // 增加库存
                            List<SynthesisPlanResultDto> planResult = synthesisPlanService.getPlanResult(synthesis.getPlanId());
                            for (SynthesisPlanResultDto result : planResult) {
                                switch (result.getType()) {
                                    case BOX:
                                        boxService.lockStock(result.getThirdId(), params.getNum() * result.getProductNum().intValue());
                                        break;
                                    case EQUIPMENT:
                                    case NORMAL:
                                        nftService.lockStock(result.getThirdId(), params.getNum() * result.getProductNum().intValue());
                                        break;
                                }
                            }
                            boolean b = synthesisStockMapper.addWhiteStock(synthesis.getId(), params.getNum());
                            if(!b){
                                throw new ServiceException("添加白名单库存失败！");
                            }
                            break;
                        case OUT:
                            b = synthesisStockMapper.rollbackWhiteStock(synthesis.getId(), params.getNum());
                            if(!b){
                                throw new ServiceException("减少白名单库存失败：库存不足！");
                            }
                            planResult = synthesisPlanService.getPlanResult(synthesis.getPlanId());
                            for (SynthesisPlanResultDto result : planResult) {
                                switch (result.getType()) {
                                    case BOX:
                                        boxService.unlockStock(result.getThirdId(), params.getNum() * result.getProductNum().intValue());
                                        break;
                                    case EQUIPMENT:
                                    case NORMAL:
                                        nftService.unlockStock(result.getThirdId(), params.getNum() * result.getProductNum().intValue());
                                        break;
                                }
                            }
                            break;
                    }
                    return true;
                });
            }
            break;
        }
        return true;
    }

    /**
     * 销毁投入材料
     */
    private void burnConsume(Long userId, List<SynthesisPlanConsumeDto> consumes, SynthesisUserRecord record) {
        for (SynthesisPlanConsumeDto consume : consumes) {
            switch (consume.getType()) {
                case NORMAL:
                case EQUIPMENT:
                    List<SysUserNft> sysUserNfts = sysUserNftService.userNftBurn(userId, consume.getInvestmentNftList(), UserNftStatusEnum.SYNTHESIS_BURN);
                    consume.setBurnUserNfts(sysUserNfts);
                    break;
                case BOX:
                    userBoxService.synthesisBurn(userId, consume.getThirdId(), consume.getNum());
                    break;
                case MATERIAL:
                    if (!Objects.equals(consume.getNum(), consume.getInvestmentNum())) {
                        throw new ServiceException("请添加合成所需数量的材料~");
                    }
                    // 判断热豆还是扳手
                    if (UserWalletEnum.valueOf(Math.toIntExact(consume.getThirdId())) == UserWalletEnum.INTEGRAL) {
                        sysUserWalletService.reduce(userId, UserWalletEnum.valueOf(Math.toIntExact(consume.getThirdId())), new BigDecimal(consume.getNum()), WalletChangeTypeEnum.SYNTHESIS_CONSUME, record.getId(), WalletChangeTypeEnum.SYNTHESIS_CONSUME.getMsg());
                    } else if (UserWalletEnum.valueOf(Math.toIntExact(consume.getThirdId())) == UserWalletEnum.WRENCH) {
                        userAssetWrenchRecordService.updateWrenchBalance(userId, WalletWrenchChangeTypeEnum.SYNTHESIS_CONSUME, WalletChangeDirectionEnum.OUT, new BigDecimal(consume.getNum()), record.getId(), WalletWrenchChangeTypeEnum.SYNTHESIS_CONSUME.getMsg());
                    } else if (UserWalletEnum.valueOf(Math.toIntExact(consume.getThirdId())) == UserWalletEnum.LIDOU) {
                        userAssetLidouService.updateLidouBalance(userId, WalletLidouChangeTypeEnum.SYNTHESIS_CONSUME, WalletChangeDirectionEnum.OUT, new BigDecimal(consume.getNum()), record.getId(), WalletLidouChangeTypeEnum.SYNTHESIS_CONSUME.getMsg());
                    }
                    break;
            }
        }
    }

    /**
     * 发放合成结构
     *
     * @return
     */
    private List<SynthesisStartResultVo> dispatchResult(Long userId, Synthesis synthesis, SynthesisUserRecord synthesisRecord, int count) {
        List<SynthesisStartResultVo> resultVoList = new ArrayList<>();
        List<SynthesisPlanResultDto> resultList = synthesisPlanService.getPlanResult(synthesis.getPlanId());
        for (SynthesisPlanResultDto result : resultList) {
            // 实际发放数量
            result.setProductNum(result.getProductNum().multiply(BigDecimal.valueOf(count)));
            switch (result.getType()) {
                case NORMAL:
                case EQUIPMENT: {
                    Nft nft = nftService.requireNft(result.getThirdId());
                    LocalDateTime saleableTime = LocalDateTime.now().plusMinutes(Optional.ofNullable(nft.getCoolingTime()).orElse(0));
                    nftService.takeStock(result.getThirdId(), result.getProductNum().intValue());
                    List<SysUserNft> sysUserNfts = nftNumberService.distributeNftNumber(userId, result.getThirdId(), synthesis.getId(), synthesisRecord.getId(), result.getProductNum().intValue(), true, saleableTime, NftDispatchSourceEnum.SYNTHESIS, UserNftSourceTypeEnum.SYNTHESIS);
                    for (SysUserNft sysUserNft : sysUserNfts) {
                        SynthesisStartResultVo resultVo = BeanCopierUtil.copy(result, SynthesisStartResultVo.class);
                        resultVo.setNftNumberId(sysUserNft.getNftNumber().getNumberValue());
                        resultVo.setNftNumberValue(sysUserNft.getNftNumber().getNumberValueStr());
                        resultVo.setEquipment(BeanCopierUtil.copy(sysUserNft.getNftNumber().getEquipment(), EquipmentInfoVo.class));
                        resultVo.setShowItem(true);
                        // 数量固定是1
                        resultVo.setProductNum(BigDecimal.ONE);
                        resultVoList.add(resultVo);
                    }
                }
                break;
                case BOX: {
                    Set<Long> nftIdList = null;
                    // 如果有箱子规则，则按规则判断是否过滤藏品
                    List<SynthesisBoxRule> boxRules = synthesisPlanResultBoxRuleService.lambdaQuery()
                            .eq(SynthesisBoxRule::getSynthesisId, synthesis.getId())
                            .list();
                    if (boxRules != null && boxRules.size() != 0) {
                        nftIdList = boxRules.stream()
                                .map(SynthesisBoxRule::getNftIds)
                                .flatMap(s -> Arrays.stream(s.split(",")).map(Long::valueOf))
                                .collect(Collectors.toSet());
                        int synthesisCount = synthesisUserRecordService.lambdaQuery()
                                .eq(SynthesisUserRecord::getUserId, userId)
                                .eq(SynthesisUserRecord::getSynthesisId, synthesis.getId())
                                .eq(SynthesisUserRecord::getStatus, SynthesisStatusEnum.SUCCESS)
                                .list().stream().mapToInt(SynthesisUserRecord::getNum).sum();
                        // 过滤出合成次数大于规则门槛次数且userId包含在规则的userIds里的规则列表
                        List<SynthesisBoxRule> boxRuleFilters = boxRules.stream()
                                .filter(rule -> synthesisCount >= rule.getNum() && (StrUtil.isBlank(rule.getUserIds()) || Arrays.asList(rule.getUserIds().split(",")).contains(userId.toString())))
                                .collect(Collectors.toList());
                        if (!boxRuleFilters.isEmpty()) {
                            Set<Long> nftFilterIds = boxRuleFilters.stream().map(SynthesisBoxRule::getNftIds).flatMap(s -> Arrays.stream(s.split(","))).map(Long::valueOf).collect(Collectors.toSet());
                            // 需要过滤nftIds
                            log.info("解除中-----合成结果宝盒过滤nftids:{}", nftFilterIds);
                            nftIdList.removeAll(nftFilterIds);
                            log.info("解除成功-----合成结果宝盒过滤nftids:{}", nftIdList);
                        }

                    }
                    Box box = boxService.getBoxById(result.getThirdId());
                    Map<BoxNft, Integer> boxNftDispatch = boxService.takeLockStore(result.getThirdId(), result.getProductNum().intValue(), nftIdList);
                    List<Pair<BoxNft, List<NftNumber>>> nftList = new ArrayList<>();
                    for (BoxNft boxNft : boxNftDispatch.keySet()) {
                        List<NftNumber> nftNumbers = nftNumberService.distributeBox(synthesis.getId(), synthesisRecord.getId(), boxNft.getNftId(), boxNftDispatch.get(boxNft), box.getSequence() == BoxSequenceEnum.RANDOM);
                        nftList.add(new Pair<>(boxNft, nftNumbers));
                    }
                    LocalDateTime saleableTime = LocalDateTime.now().plusMinutes(Optional.ofNullable(box.getCoolingTime()).orElse(0));
                    UserBox userBox = userBoxService.addUserBox(userId, synthesis.getId(), synthesisRecord.getId(), box, nftList, result.getProductNum().intValue(), saleableTime);
                    if(BooleanUtils.isTrue(box.getAutoOpen())){
                        // 如果开启自动开箱
                        OpenBoxDto openBoxReq = new OpenBoxDto();
                        openBoxReq.setUserBoxId(userBox.getId());
                        openBoxReq.setQuantity(result.getProductNum().intValue());
                        openBoxReq.setUseInteger(BigDecimal.ZERO);
                        List<UserNFTItemVo> userNFTItemVos = userBoxService.openBox(userId, openBoxReq);
                        for (UserNFTItemVo sysUserNft : userNFTItemVos) {
                            SynthesisStartResultVo resultVo = BeanCopierUtil.copy(result, SynthesisStartResultVo.class);
                            resultVo.setName(sysUserNft.getTitle());
                            resultVo.setImgUrl(sysUserNft.getMainImg());
                            switch (sysUserNft.getNftType()){
                                case EQUIPMENT:
                                    resultVo.setType(SynthesisTypeEnum.EQUIPMENT);
                                    break;
                                case NORMAL:
                                    resultVo.setType(SynthesisTypeEnum.NORMAL);
                                    break;
                            }
                            resultVo.setNftNumberId(sysUserNft.getNftNumberId());
                            resultVo.setNftNumberValue(sysUserNft.getNftNumberValue());
                            resultVo.setEquipment(BeanCopierUtil.copy(sysUserNft.getEquipment(), EquipmentInfoVo.class));
                            resultVo.setShowItem(true);
                            // 数量固定1
                            resultVo.setProductNum(BigDecimal.ONE);
                            resultVoList.add(resultVo);
                        }
                    }else{
                        SynthesisStartResultVo resultVo = BeanCopierUtil.copy(result, SynthesisStartResultVo.class);
                        resultVo.setShowItem(false);
                        resultVoList.add(resultVo);
                    }
                }
                break;
                case MATERIAL: {
                    if (UserWalletEnum.valueOf(Math.toIntExact(result.getThirdId())) == UserWalletEnum.INTEGRAL) {
                        sysUserWalletService.increase(userId, UserWalletEnum.valueOf(Math.toIntExact(result.getThirdId())), result.getProductNum(), WalletChangeTypeEnum.SYNTHESIS_RESULT, synthesisRecord.getId(), WalletChangeTypeEnum.SYNTHESIS_RESULT.getMsg());
                    } else if (UserWalletEnum.valueOf(Math.toIntExact(result.getThirdId())) == UserWalletEnum.WRENCH) {
                        userAssetWrenchRecordService.updateWrenchBalance(userId, WalletWrenchChangeTypeEnum.SYNTHESIS_RESULT, WalletChangeDirectionEnum.IN, result.getProductNum(), synthesisRecord.getId(), WalletWrenchChangeTypeEnum.SYNTHESIS_RESULT.getMsg());
                    } else if (UserWalletEnum.valueOf(Math.toIntExact(result.getThirdId())) == UserWalletEnum.LIDOU) {
                        userAssetLidouService.updateLidouBalance(userId, WalletLidouChangeTypeEnum.SYNTHESIS_RESULT, WalletChangeDirectionEnum.IN, result.getProductNum(), synthesisRecord.getId(), WalletLidouChangeTypeEnum.SYNTHESIS_RESULT.getMsg());
                    }

                    SynthesisStartResultVo resultVo = BeanCopierUtil.copy(result, SynthesisStartResultVo.class);
                    resultVo.setShowItem(false);
                    resultVoList.add(resultVo);
                }
                break;
            }
        }
        return resultVoList;
    }

    /**
     * 移除持图配置-材料
     *
     * @param params
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public boolean delHoldCondition(WebSynthesisEditDto params) {
        if (Objects.isNull(params) || params.getId() == null || params.getId() < 0) {
            throw new ServiceException("参数错误");
        }
        Synthesis synthesis = getById(params.getId());
        if (Objects.isNull(synthesis)) {
            throw new ServiceException("活动不存在");
        }
        switch (params.getEditType()) {
            case DEL_HOLD_CONDITION:
                LambdaQueryWrapper<SynthesisHoldCondition> wp = new LambdaQueryWrapper<>();
                wp.eq(SynthesisHoldCondition::getSynthesisId, params.getId());
                wp.eq(SynthesisHoldCondition::getThirdId, params.getThirdId());
                synthesisHoldConditionService.remove(wp);
            break;
            case DEL_PALN:
                synthesisPlanStatusService.remove(new LambdaQueryWrapper<SynthesisPlanStatus>()
                        .eq(SynthesisPlanStatus::getSynthesisId, params.getId())
                        .eq(SynthesisPlanStatus::getPlanId, synthesis.getPlanId()));
                update(new LambdaUpdateWrapper<Synthesis>()
                        .eq(Synthesis::getId, synthesis.getId())
                        .set(Synthesis::getPlanId, null));
                break;
            case DEL_SYNTHESIS:
                if (synthesis.getEndTime() != null) {
                    if (LocalDateUtils.compareDateTime(synthesis.getEndTime(), LocalDateTime.now())) {
                        throw new ServiceException("活动进行中，无法删除");
                    }
                } else {
                    if (LocalDateUtils.compareDateTime(LocalDateTime.now(), synthesis.getBeginTime())) {
                        throw new ServiceException("活动进行中，无法删除");
                    }
                }
                removeById(params.getId());
                synthesisHoldService.removeById(params.getId());
                synthesisHoldConditionService.remove(new LambdaQueryWrapper<SynthesisHoldCondition>()
                        .eq(SynthesisHoldCondition::getSynthesisId, params.getId()));
                synthesisPlanStatusService.remove(new LambdaQueryWrapper<SynthesisPlanStatus>()
                        .eq(SynthesisPlanStatus::getSynthesisId, params.getId()));
                break;
            case DEL_WHITE:
                synthesisWhiteService.remove(new LambdaQueryWrapper<SynthesisWhite>()
                        .eq(SynthesisWhite::getSynthesisId, synthesis.getId()));
                break;
        }
        return true;
    }

    /**
     * app详情版本2
     * @param userId
     * @param synthesisId
     * @return
     */
    public SynthesisDetailVo getAppDetailV2(Long userId, Long synthesisId) {
        SynthesisDetailVo detailVo = baseMapper.selectDetailInfo(synthesisId);
        if (Objects.isNull(detailVo) || BooleanUtils.isNotTrue(detailVo.getShow())) {
            throw new ServiceException("活动不存在！");
        }
        SynthesisWhite whiteInfo = null;
        if (detailVo.getWhite()) {
            // 判断当前用户是否是白名单用户
            whiteInfo = synthesisWhiteService.getWhiteInfo(userId, synthesisId, SynthesisWhiteTypeEnum.WHITE_LIMIT);
            if (Objects.nonNull(whiteInfo)) {
                if(whiteInfo.getNum() <= whiteInfo.getNowNum()){
                    whiteInfo = null;
                }else if (Objects.nonNull(detailVo.getWhiteTime())) {
                    if (detailVo.getWhiteTime().isBefore(detailVo.getBeginTime())) {
                        detailVo.setBeginTime(detailVo.getWhiteTime());
                    }
                }
            }
        }
        // [1:未开始;2:进行中;3:已结束 4库存不足 5材料不足]
        LocalDateTime now = LocalDateTime.now();
        if (now.isBefore(detailVo.getBeginTime())) {
            detailVo.setStatus(1);
        } else if (Objects.nonNull(detailVo.getEndTime())) {
            if(Objects.nonNull(whiteInfo)){
                // 如果当前用户是白名单用户，则判断普通库存售完并且白名单库存也售完才结束
                detailVo.setStatus(now.isAfter(detailVo.getEndTime()) || (detailVo.getNum() <= 0 && detailVo.getWhiteNum() <= 0) ? 3 : 2);
            }else{
                detailVo.setStatus(now.isAfter(detailVo.getEndTime()) || detailVo.getNum() <= 0 ? 3 : 2);
            }
        } else {
            detailVo.setStatus(2);
        }

       if (detailVo.getWhiteOnly()) {
            // 需要显示白名单库存
            detailVo.setNum(detailVo.getWhiteNum());
            detailVo.setTotalNum(detailVo.getWhiteNum() + detailVo.getSalesNum());
       } else if (detailVo.getShowRealNum()) {
           // 需要显示真实库存
           detailVo.setNum(detailVo.getNum()+detailVo.getWhiteNum());
           detailVo.setSalesNum(detailVo.getTotalNum() - detailVo.getNum() - detailVo.getWhiteNum());
       } else {
           detailVo.setNum(detailVo.getTotalNum());
           detailVo.setSalesNum(0);
       }
        if (Objects.nonNull(detailVo.getMsgId())) {
            SysAnnouncementMsg announcementDetail = sysAnnouncementMsgService.getAnnouncementDetail(detailVo.getMsgId());
            detailVo.setMsgDetail(BeanUtil.copyProperties(announcementDetail, SysAnnouncementMsgVo.class));
        }
        // 查询消耗列表 并转成List<List>格式
        List<SynthesisPlanConsumeDto> synthesisPlanConsumeDtos = synthesisPlanConsumeMapper.selectDetailList(detailVo.getPlanId());
        Map<Long, List<SynthesisPlanConsumeDto>> collect = synthesisPlanConsumeDtos.stream().collect(Collectors.groupingBy(SynthesisPlanConsumeDto::getGroupId));
        List<SynthesisPlanConsumeDetailVo> synthesisPlanConsumeDetailVos = baseMapper.selectPlanGroupDetail(synthesisId, detailVo.getPlanId());
        for (SynthesisPlanConsumeDetailVo synthesisPlanConsumeDetailVo : synthesisPlanConsumeDetailVos) {
            List<SynthesisPlanConsumeDto> consumeDtos = collect.get(synthesisPlanConsumeDetailVo.getGroupId());
            // 排序，装备>藏品>宝盒>材料
            consumeDtos.sort(Comparator.comparingInt(o -> {
                if (o.getType() == SynthesisTypeEnum.EQUIPMENT) {
                    return 1;
                } else if (o.getType() == SynthesisTypeEnum.NORMAL) {
                    return 2;
                } else {
                    return o.getType().ordinal();
                }
            }));
            synthesisPlanConsumeDetailVo.setConsumes(consumeDtos);
        }
        List<SynthesisPlanResultDto> synthesisPlanResultDtos = synthesisPlanResultMapper.selectDetailList(detailVo.getPlanId());
        detailVo.setConsumeList(synthesisPlanConsumeDetailVos);
        detailVo.setResultList(synthesisPlanResultDtos);
        // 获取当前活动用户剩余参与次数
        int index = 0;
        if(Objects.isNull(whiteInfo)){
            whiteInfo = synthesisWhiteService.getWhiteInfo(userId, synthesisId, SynthesisWhiteTypeEnum.WHITE_LIMIT);
        }
        int userSynthesisCount = whiteInfo == null ? 0 : whiteInfo.getNowNum();
        // 算剩余次数
        Integer remainNum = null;
        if (Objects.nonNull(detailVo.getUserMaxNum())) {
            remainNum = Math.max(detailVo.getUserMaxNum() - userSynthesisCount, 0);
        }
        if (Objects.nonNull(detailVo.getUserSingleMaxNum())) {
            int singleNum = Math.max(detailVo.getUserSingleMaxNum() - userSynthesisCount, 0);
            remainNum = remainNum == null ? singleNum : Math.min(remainNum, singleNum);
        }
        // 如果非白名单且有持图验证则计算可合成次数
        if (whiteInfo == null && detailVo.getHold() == 1) {
            int holdNum = holdRecycleNum(synthesisId, userId);
            if (holdNum >= 0) {
                int singleNum = Math.max(holdNum - userSynthesisCount, 0);
                remainNum = remainNum == null ? singleNum : Math.min(remainNum, singleNum);
            }
        }
        detailVo.setRemainNum(remainNum);

        for (SynthesisPlanConsumeDetailVo synthesisPlanConsumeDetailVo : synthesisPlanConsumeDetailVos) {
            synthesisPlanConsumeDetailVo.setTitle(StrFormatter.format("方案{}", ++index));
            Integer actStatus = detailVo.getStatus();
            Integer availableCount;
            if (Objects.isNull(detailVo.getUserMaxNum())) {
                availableCount = Integer.MAX_VALUE;
            } else {
                availableCount = detailVo.getUserMaxNum() - userSynthesisCount;
            }
            if (detailVo.getUserSingleMaxNum() != null) {
                availableCount = Math.min(availableCount, detailVo.getUserSingleMaxNum());
            }
            // 白名单的库存判断
            if (detailVo.getWhite() && whiteInfo != null && detailVo.getWhiteOnly()) {
                availableCount = Math.min(availableCount, whiteInfo.getNum() - whiteInfo.getNowNum());
            }
            availableCount = Math.min(availableCount, detailVo.getNum());
            if (availableCount > 0) {
                // 获取当前用户当前方案还可以参与次数
                for (SynthesisPlanConsumeDto consume : synthesisPlanConsumeDetailVo.getConsumes()) {
                    SynthesisTypeEnum type = consume.getType();
                    int count = 0;
                    switch (type) {
                        case NORMAL:
                            count = sysUserNftService.countHoldNftList(userId, consume.getThirdId(), null);
                            break;
                        case EQUIPMENT:
                            count = sysUserNftService.countHoldEquipmentList(userId, consume, consume.getThirdId(), null);
                            break;
                        case BOX:
                            count = userBoxService.getHoldBoxCount(userId, consume.getThirdId());
                            break;
                        case MATERIAL:
                            count = sysUserWalletService.getWallet(userId, UserWalletEnum.valueOf(Math.toIntExact(consume.getThirdId())))
                                    .getAmount().setScale(0, RoundingMode.DOWN).intValue();
                            break;
                    }
                    consume.setUserNum(count);
                    availableCount = Math.min(availableCount, count / consume.getNum());
                    if (availableCount <= 0 && actStatus == 2) {
                        actStatus = 5;
                        break;
                    }
                }
            } else {
                if (actStatus == 2) {
                    // 白名单库存耗尽时展示立即合成
                    if (detailVo.getWhite() && whiteInfo != null && detailVo.getWhiteOnly() && (whiteInfo.getNum() - whiteInfo.getNowNum()) <= 0) {
                    } else {
                        actStatus = 4;
                    }
                }
            }
            synthesisPlanConsumeDetailVo.setAvailableCount(availableCount);
            // 如果
            synthesisPlanConsumeDetailVo.setActStatus(actStatus);
        }

        return detailVo;
    }


    /**
     * 权限检查
     * @param userId
     * @param synthesisId
     */
    public void queryPerm(Long userId, Long synthesisId) {
        Synthesis synthesis = getById(synthesisId);
        boolean isWhiteBuy = isWhiteBuy(userId, synthesis);
        // 检查活动状态
        checkValid(userId, synthesis, isWhiteBuy, 1);
    }

    /**
     * 查库存
     * @param userId
     * @param synthesisId
     * @return
     */
    public int queryStock(Long userId, Long synthesisId) {
        SynthesisDetailVo detailVo = baseMapper.selectDetailInfo(synthesisId);
        if (Objects.isNull(detailVo) || BooleanUtils.isNotTrue(detailVo.getShow())) {
            throw new ServiceException("活动不存在！");
        }
        boolean isWhiteBuy = isWhiteBuy(userId, getById(synthesisId));
        // 判断白名单用户库存耗尽的情况
        if (isWhiteBuy && detailVo.getWhite() && detailVo.getWhiteOnly()) {
            SynthesisWhite whiteInfo = synthesisWhiteService.getWhiteInfo(userId, synthesisId, SynthesisWhiteTypeEnum.WHITE_LIMIT);
            if (whiteInfo != null && whiteInfo.getNum() - whiteInfo.getNowNum() <= 0) {
                return 0;
            }
        }
        int stock = isWhiteBuy ? detailVo.getWhiteNum() : detailVo.getNum();
        // 如果活动开始了，且是白名单用户则需要加上普通库存
        if (detailVo.getBeginTime().isBefore(LocalDateTime.now()) && isWhiteBuy) {
            stock += detailVo.getNum();
        }
        return stock;
    }


    /**
     * 开始合成v2
     */
    @RedisLock(lockKey = LockKey.LOCK_USER_SYNTHESIS)
    public SynthesisStartVo startSynthesisV2(@LockValue Long userId, SynthesisStartDto dto) throws Exception {
        Synthesis synthesis = getById(dto.getSynthesisId());
        boolean isWhiteBuy = isWhiteBuy(userId, synthesis);
        // 合成数量
        final int count = Math.max(dto.getCount(), 1);
        // 检查材料是否满足方案条件
        checkValid(userId, synthesis, isWhiteBuy, count);
        // 判断前端传的材料列表是否和后台需要的材料列表一致
        List<SynthesisPlanConsumeDto> consumeList = synthesisPlanConsumeMapper.selectGroupConsume(synthesis.getPlanId(), dto.getGroupId());
        if (consumeList.isEmpty() || (consumeList.size() != dto.getConsumes().size())) {
            throw new ServiceException("合成材料信息错误！");
        }
        for (SynthesisPlanConsumeDto consume : consumeList) {
            SynthesisPlanConsumeDto consumeDto = dto.getConsumes().stream()
                    .filter(userInput -> Objects.equals(userInput.getConsumeId(), consume.getConsumeId()))
                    .findFirst().orElseThrow(() -> new ServiceException("缺少合成材料！"));
            // 实际需要消耗的数量
            consume.setNum(consume.getNum() * count);
            consumeDto.setNum(consume.getNum());
            switch (consume.getType()) {
                case NORMAL:
                case EQUIPMENT:
                    // 如果前端没传消耗列表就按照默认顺序填装
                    if (consumeDto.getInvestmentNftList() == null || consumeDto.getInvestmentNftList().isEmpty()) {
                        SynthesisPlanConsumeUserNftDto consumeUserNftDto = new SynthesisPlanConsumeUserNftDto();
                        consumeUserNftDto.setSynthesisConsumeId(consume.getConsumeId());
                        consumeUserNftDto.setUserId(userId);
                        List<UserNFTItemVo> nftList = this.consumeUserNftList(consumeUserNftDto).getNftList();
                        // 筛选前consume.getNum()个
                        consumeDto.setInvestmentNftList(nftList.stream().limit(consume.getNum()).map(UserNFTItemVo::getUserNftId).collect(Collectors.toList()));
                    }
                    if (consume.getNum() != consumeDto.getInvestmentNftList().size()) {
                        throw new ServiceException("请添加合成所需数量的材料~");
                    }
                    // 判断这些nft是否时用户持有，并且状态正常
                    int nftCount = sysUserNftService.countHoldNftList(userId, consume.getThirdId(), consumeDto.getInvestmentNftList());
                    if (nftCount != consumeDto.getInvestmentNftList().size()) {
                        throw new ServiceException("您添加的藏品不满足合成规则！");
                    }
                    break;
                case BOX:
                    if (!Objects.equals(consume.getNum(), consumeDto.getInvestmentNum())) {
                        throw new ServiceException("请添加合成所需数量的材料~");
                    }
                    // 检测持有数量
                    UserBox userBox = userBoxService.getUserBoxByBoxId(userId, consume.getThirdId());
                    if (userBox.getQuantity() < consume.getNum()) {
                        throw new ServiceException("所需宝箱数量不足~");
                    }
                    break;
                case MATERIAL:
                    if (!Objects.equals(consume.getNum(), consumeDto.getInvestmentNum())) {
                        throw new ServiceException("请添加合成所需数量的材料~");
                    }
                    SysUserWallet wallet = sysUserWalletService.getWallet(userId, UserWalletEnum.valueOf(Math.toIntExact(consume.getThirdId())));
                    if (wallet.getAmount().compareTo(new BigDecimal(consume.getNum())) < 0) {
                        throw new ServiceException("所需材料数量不足~");
                    }
                    break;
                default:
                    throw new ServiceException("暂不支持的材料类型！");
            }
        }
        // 扣除库存
        AtomicBoolean useWhiteStock = new AtomicBoolean(false);
        redisLockUtils.lock(LockKey.LOCK_USER_SYNTHESIS + ":" + synthesis.getId(), () -> {
            transactionalManage.executeWithException(() -> {
                // 如果是白名单用户，则先扣除白名单库存
                if (isWhiteBuy) {
                    int update = synthesisStockMapper.update(null, Wrappers.lambdaUpdate(SynthesisStock.class)
                            .setSql("white_num = white_num - " + count)
                            .setSql("lock_num = lock_num + " + count)
                            .eq(SynthesisStock::getId, synthesis.getId())
                            .ge(SynthesisStock::getWhiteNum, count));
                    if (update == 1) {
                        // 白名单库存扣除成功，则直接返回
                        useWhiteStock.set(true);
                        return true;
                    }
                }
                int update = synthesisStockMapper.update(null, Wrappers.lambdaUpdate(SynthesisStock.class)
                        .setSql("num = num - " + count)
                        .setSql("lock_num = lock_num + " + count)
                        .eq(SynthesisStock::getId, synthesis.getId())
                        .ge(SynthesisStock::getNum, count));
                if (update != 1) {
                    throw new ServiceException("合成剩余库存不足~");
                }
                return true;
            });
        });
        // 生成合成记录
        SynthesisUserRecord synthesisRecord = synthesisUserRecordService.buildRecord(userId, synthesis, dto, SynthesisActivityTypeEnum.NORMAL);

        SynthesisStartVo vo = new SynthesisStartVo();
        vo.setSynthesisId(synthesis.getId());
        vo.setGroupId(dto.getGroupId());
        vo.setRecordId(synthesisRecord.getId());
        // 销毁投入
        transactionalManage.executeWithException(() -> {
            // 销毁投入
            burnConsume(userId, dto.getConsumes(), synthesisRecord);
            // 发放合成结果
            List<SynthesisStartResultVo> resultList = dispatchResult(userId, synthesis, synthesisRecord, count);
            // 存储合成记录
            synthesisRecord.setStatus(SynthesisStatusEnum.SUCCESS);
            boolean success = synthesisUserRecordService.saveBasic(synthesisRecord);
            if (!success) {
                throw new ServiceException("合成失败！");
            }
            // 存储合成输入和输出
            synthesisRecordConsumeResultService.saveRecord(synthesisRecord, dto.getConsumes(), resultList);
            vo.setResult(resultList);
            return true;
        }, e -> {
            // 存储失败信息
            synthesisRecord.setStatus(SynthesisStatusEnum.ERROR);
            StringWriter stringWriter = new StringWriter();
            PrintWriter writer = new PrintWriter(stringWriter);
            e.printStackTrace(writer);
            writer.flush();
            writer.close();
            synthesisRecord.setErrMsg(stringWriter.toString());
            synthesisUserRecordService.saveBasic(synthesisRecord);

            log.error("合成失败，请人工检查：{}", synthesis.getId(), e);
            // 回退库存
            int update;
            if (useWhiteStock.get()) {
                update = synthesisStockMapper.update(null, Wrappers.lambdaUpdate(SynthesisStock.class)
                        .setSql("white_num = white_num + " + count)
                        .setSql("lock_num = lock_num - " + count)
                        .eq(SynthesisStock::getId, synthesis.getId())
                        .ge(SynthesisStock::getLockNum, count));

            } else {
                update = synthesisStockMapper.update(null, Wrappers.lambdaUpdate(SynthesisStock.class)
                        .setSql("num = num + " + count)
                        .setSql("lock_num = lock_num - " + count)
                        .eq(SynthesisStock::getId, synthesis.getId())
                        .ge(SynthesisStock::getLockNum, count));
            }
            if (update != 1) {
                log.error("回退库存失败：{}", synthesis.getId());
                throw new ServiceException("合成失败~");
            }
        });

        // 修改用户购买库存
        synthesisWhiteService.addSynthesisSuccess(userId, synthesis, isWhiteBuy, count);

        // 扣除锁定库存
        transactionalManage.executeWithException(() -> {
            LambdaUpdateWrapper<SynthesisStock> wrapper = Wrappers.lambdaUpdate(SynthesisStock.class)
                    .setSql("lock_num = lock_num - " + count)
                    .setSql("sales_num = sales_num + " + count)
                    .eq(SynthesisStock::getId, synthesis.getId())
                    .ge(SynthesisStock::getLockNum, count);
            if (!isWhiteBuy) {
                // 如果有非白名单购买，则展示真实库存
                wrapper.set(SynthesisStock::getShowRealNum, true);
            }
            int update = synthesisStockMapper.update(null, wrapper);
            return update == 1;
        });

        return vo;
    }

    /**
     * 消耗用户nft列表
     * @param param
     * @return
     */
    public SynthesisPlanConsumeUserNftVo consumeUserNftList(SynthesisPlanConsumeUserNftDto param) {
        SynthesisPlanConsume planConsume = synthesisPlanConsumeMapper.selectById(param.getSynthesisConsumeId());
        if (Objects.isNull(planConsume)) {
            throw new ServiceException("合成材料不存在！");
        }
        UserNftListDto userNftListDto = new UserNftListDto();
        userNftListDto.setUserId(SecurityUtils.getUserId());
        userNftListDto.setNftId(planConsume.getThirdId());
        userNftListDto.setNftType(NftTypeEnum.getByCode(planConsume.getType().getValue()));
        userNftListDto.setSortByEquip(true);
        userNftListDto.setPageNum(1);
        userNftListDto.setPageSize(Integer.MAX_VALUE);
        if (SynthesisTypeEnum.EQUIPMENT == planConsume.getType()) {
            userNftListDto.setMinLevel(planConsume.getMinLevel());
            userNftListDto.setMaxLevel(planConsume.getMaxLevel());
            userNftListDto.setMinDurable(planConsume.getMinDurable());
            userNftListDto.setMaxDurable(planConsume.getMaxDurable());
            userNftListDto.setMinBreakage(planConsume.getMinBreakage());
            userNftListDto.setMaxBreakage(planConsume.getMaxBreakage());
            userNftListDto.setOldEquipment(param.getOldEquipment());
        }

        List<UserNFTItemVo> nftList = sysUserNftMapper.getNftNumberList(userNftListDto);
        SynthesisPlanConsumeUserNftVo vo = new SynthesisPlanConsumeUserNftVo();
        vo.setNftList(nftList);
        if (SynthesisTypeEnum.EQUIPMENT == planConsume.getType()) {
            if (!nftList.isEmpty()) {
                vo.setMinLevel(nftList.stream().mapToInt(t -> t.getEquipment().getLevel()).min().getAsInt());
                vo.setMaxLevel(nftList.stream().mapToInt(t -> t.getEquipment().getLevel()).max().getAsInt());
            } else {
                vo.setMinLevel(planConsume.getMinLevel());
                vo.setMaxLevel(planConsume.getMaxLevel());
            }
            // 根据传参，过滤装备等级
            if (Objects.nonNull(param.getMaxLevel())) {
                vo.setNftList(nftList.stream().filter(t -> t.getEquipment().getLevel() <= param.getMaxLevel()).collect(Collectors.toList()));
            }
            // 1先按装备等级倒序，装备等级越低排序优先 2.同等级装备按装备新旧排序，旧装备排序优先新装备 3.同等级有多个新装备或旧装备，按装备总属性倒序排总属性越低，排序越靠前
            vo.setNftList(vo.getNftList().stream().sorted(Comparator.comparing(UserNFTItemVo::getEquipment, (o1, o2) -> {
                if (o1.getLevel().equals(o2.getLevel())) {
                    // 先判断新旧装备，总破损=当前破损且总耐久=当前耐久则为新装备
                    boolean o1IsNew = o1.getBreakage().equals(o1.getTotalBreakage()) && o1.getDurability().equals(o1.getTotalDurability());
                    boolean o2IsNew = o2.getBreakage().equals(o2.getTotalBreakage()) && o2.getDurability().equals(o2.getTotalDurability());
                    if (o1IsNew && !o2IsNew) {
                        return 1;
                    } else if (!o1IsNew && o2IsNew) {
                        return -1;
                    } else {
                        return o1.getTotalAttrCount().compareTo(o2.getTotalAttrCount());
                    }
                }
                return o1.getLevel().compareTo(o2.getLevel());
            })).collect(Collectors.toList()));
        }
        return vo;

    }

    /**
     * 检查是否有权限合成
     * @param userId
     * @param synthesis
     * @param isWhiteBuy
     */
    private void checkValid(Long userId, Synthesis synthesis, boolean isWhiteBuy, int count) {
        if (synthesis.getShow() != 1) {
            throw new ServiceException("活动不存在~");
        }
        // 售卖时间判断
        LocalDateTime now = LocalDateTime.now();
        if (isWhiteBuy) {
            if (Objects.nonNull(synthesis.getEndTime())) {
                if (now.isAfter(synthesis.getEndTime())) {
                    throw new ServiceException("活动已结束~");
                }
            }
            // 如果是白名单用户，则以最早开始时间为准
            LocalDateTime beginTime;
            if (Objects.nonNull(synthesis.getWhiteTime())) {
                if (synthesis.getWhiteTime().isBefore(synthesis.getBeginTime())) {
                    beginTime = synthesis.getWhiteTime();
                } else {
                    beginTime = synthesis.getBeginTime();
                }
            } else {
                beginTime = synthesis.getBeginTime();
            }
            if (now.isBefore(beginTime)) {
                throw new ServiceException("活动还未开始~");
            }
        } else {
            if (now.isBefore(synthesis.getBeginTime())) {
                throw new ServiceException("活动还未开始~");
            }
            if (Objects.nonNull(synthesis.getEndTime())) {
                if (now.isAfter(synthesis.getEndTime())) {
                    throw new ServiceException("活动已结束~");
                }
            }
        }
        // 判断是否仅白名单
        if (synthesis.getWhiteOnly() && !isWhiteBuy) {
            throw new ServiceException("不具备合成资格");
        }
        // 单次限购判断
        if (Objects.nonNull(synthesis.getUserSingleMaxNum()) && synthesis.getUserSingleMaxNum() < count) {
            throw new ServiceException("已达到单次最大合成次数限制~");
        }
        // 身份等级判断
        if (synthesis.getIdentityRankId() != null && !userIdentityRankService.checkUserRankGETargetRank(userId, synthesis.getIdentityRankId())) {
            UserIdentityRank userIdentityRank = userIdentityRankService.getById(synthesis.getIdentityRankId());
            throw new ServiceException(userIdentityRank.getIdentityName() + userIdentityRank.getName() + "以上身份才能参与");
        }
        // 持图购判断是否还可以购买,白名单则不需要持图验证
        if (CommonBooleanEnum.isTrue(synthesis.getHold()) && !isWhiteBuy) {
            int recycleNum = holdRecycleNum(synthesis.getId(), userId);
            if (recycleNum == 0) {
                throw new ServiceException("不满足持图条件，请关注官方公告");
            } else if (recycleNum > 0 && recycleNum < count) {
                throw new ServiceException("合成数量不满足持图条件，请关注官方公告");
            } else if (recycleNum > 0) {
                SynthesisWhite whiteInfo = synthesisWhiteService.getWhiteInfo(userId, synthesis.getId(), SynthesisWhiteTypeEnum.ACTIVITY_LIMIT);
                if (whiteInfo != null && whiteInfo.getNowNum() >= recycleNum) {
                    throw new ServiceException("合成数量不满足持图条件，请关注官方公告");
                }
            }
        }
        // 限购判断
        if (Objects.nonNull(synthesis.getUserMaxNum())) {
            SynthesisWhite whiteInfo = synthesisWhiteService.getWhiteInfo(userId, synthesis.getId(), SynthesisWhiteTypeEnum.ACTIVITY_LIMIT);
            if (whiteInfo != null && whiteInfo.getNowNum() >= synthesis.getUserMaxNum()) {
                throw new ServiceException("已达到最大合成次数限制~");
            }
        }

    }

    /**
     * 查出持图的库存的倍数
     * @param synthesisId
     * @param userId
     * @return
     */
    private int holdRecycleNum(Long synthesisId, Long userId) {
        int recycleNum = -1;
        SynthesisHold synthesisHold = synthesisHoldService.getById(synthesisId);
        if (Objects.nonNull(synthesisHold)) {
            List<SynthesisHoldCondition> holdConditions = synthesisHoldConditionService.lambdaQuery()
                    .eq(SynthesisHoldCondition::getSynthesisId, synthesisId)
                    .list();
            for (SynthesisHoldCondition holdCondition : holdConditions) {
                int nftCount;
                if (holdCondition.getMaterialType() == 2) {
                    nftCount = sysUserNftService.countHoldEquipmentList(userId, holdCondition.getThirdId(), holdCondition, synthesisHold.getHoldTime());
                } else {
                    nftCount = sysUserNftService.countHoldNftListByDate(userId, holdCondition.getThirdId(), synthesisHold.getHoldTime());
                }
                if (nftCount < holdCondition.getNum()) {
                    return 0;
                }
                if (synthesisHold.getType() == 2) {
                    if (recycleNum == -1) {
                        recycleNum = nftCount / holdCondition.getNum();
                    } else {
                        recycleNum = Math.min(recycleNum, nftCount / holdCondition.getNum());
                    }
                }
            }
        }
        return recycleNum;
    }

    /**
     * 是否是白名单用户
     * @param userId
     * @param synthesis
     * @return
     */
    private boolean isWhiteBuy(Long userId, Synthesis synthesis){
        // 是否是白名单优先购
        final boolean isWhiteBuy;
        if (CommonBooleanEnum.isTrue(synthesis.getWhite())) {
            // 如果开启白名单了
            // 判断当前用户是否是白名单用户
            SynthesisWhite whiteInfo = synthesisWhiteService.getWhiteInfo(userId, synthesis.getId(), SynthesisWhiteTypeEnum.WHITE_LIMIT);
            // 有白名单配置并且还有白名单购买空间
            isWhiteBuy = whiteInfo != null && whiteInfo.getNum() > whiteInfo.getNowNum();
        } else {
            isWhiteBuy = false;
        }
        return isWhiteBuy;
    }

    /**
     * 修改仅白名单
     * @param id
     * @param value
     * @return
     */
    public Boolean updateWhiteOnly(Long id, Boolean value) {
        return this.lambdaUpdate().eq(Synthesis::getId, id).set(Synthesis::getWhiteOnly, value).update();
    }
}